Python3によるオブジェクト指向プログラミング - メタクラス
著者: Leonardo Giordani - 01/09/2014 Updated on May 22, 2019
この記事はIPython Notebookとして公開 います。 タイプ・ブラザーズ
Python オブジェクトの最も親密な秘密への最初のステップは、最初の投稿ですでに出会った 2 つのコンポーネント、クラスとオブジェクトから来ています。この2つの要素はPythonのOOPシステムの非常に基本的な要素であり、それらがどのように動作し、お互いに関係しているかを理解するために時間を費やす価値があります。
まず最初に、Pythonではすべてがオブジェクトであり、すべてがオブジェクトを継承していることを思い出してください。したがって、オブジェクトはPythonの変数の中でも最も深く掘り下げられたもののようです。以下を確認してみましょう。
code: python
>> a = 5
>> type(a)
<class 'int'>
>> a.__class__
<class 'int'>
>> a.__class__.__bases__
(<class 'object'>,)
>> object.__bases__
()
変数aはintクラスのインスタンスで、intクラスはobjectを継承しており、objectは何も継承していません。これは、objectがクラス階層の最上位にあることを示しています。しかし、見ての通り、intもobjectもクラスと呼ばれています(<class 'int'>, <class 'object'>)。実際、a は int クラスのインスタンスですが、int 自体は別のクラスのインスタンスであり、クラスを構築するためにインスタンス化されるクラスです
code: python
>> type(a)
<class 'int'>
>> type(int)
<class 'type'>
>> type(float)
<class 'type'>
>> type(dict)
<class 'type'>
Pythonではすべてがオブジェクトなので、クラスであってもすべてがクラスのインスタンスになります。さて、typeはクラスを得るためにインスタンス化されるクラスです。つまり、オブジェクトはすべてのオブジェクトのベースであり、typeはすべての型のクラスです。不思議に思いませんか?それはあなたのせいではありません。しかし、最後の仕上げとして、Pythonは次のように構築されています。
code: python
>> type(object)
<class 'type'>
>> type.__bases__
(<class 'object'>,)
もしあなたがこの時点で気絶しそうになっていなければ、おそらくあなたはPythonコア開発チームのGuido van Rossumの友人の一人でしょう(この場合は、あなたの美しい作品に感謝しましょう)。必要であれば、お茶を一杯もらえるかもしれません。
冗談はさておき、Pythonの型システムの根底には、オブジェクトと型という2つのものがあり、これらは切り離せないものです。先ほどのコードでは、object はtype のインスタンスであり、type は object を継承しています。この微妙な概念を理解するのに時間をかけてください。なぜなら、この概念はこれからのメタクラスに関する議論で非常に重要だからです。
型とオブジェクトの問題を理解したと思ったら、これを読んでもう一度考えてみてください。
code: python
>> type(type)
<class 'type'>
Pythonのメタクラスについて
皆さんはPythonのクラスに慣れ親しんできました。クラスはインスタンスを生成するために使用され、後者の構造はソースクラスとそのすべての親クラスに支配されていることを知っています(オブジェクトに到達するまで)。
クラスはオブジェクトでもあるので、クラス自体が(スーパー)クラスのインスタンスであり、このクラスがタイプであることもご存知でしょう。つまり、すでに述べたように、typeはクラスを構築するために使われるクラスなのです。
例えば、クラスはインスタンス化される可能性があることを知っています。クラスが呼び出される準備をするものは?クラスにすべてのメソッドを与えるものは何でしょうか?Pythonでは、このようなタスクを実行するクラスをメタクラスと呼び、typeはすべてのクラスのデフォルトのメタクラスです。
このようなPythonオブジェクトの構造を公開する意味は、クラスの構築方法を変えることができるからです。ご存知の通り、typeはオブジェクトなので、他のクラスと同様にサブクラス化することができます。type のサブクラスを取得したら、それを type の代わりにメタクラスとして使用するようにクラスに指示する必要がありますが、これはクラス定義で metaclass キーワード引数として渡すことで可能です。
code: python
>> class MyType(type):
... pass
...
>> class MySpecialClass(metaclass=MyType):
... pass
...
>> msp = MySpecialClass()
>> type(msp)
<class '__main__.MySpecialClass'>
>> type(MySpecialClass)
<class '__main__.MyType'>
>> type(MyType)
<class 'type'>
メタクラス 2: シングルトンの日
メタクラスは、Pythonでは非常に高度なトピックですが、多くの実用的な用途があります。例えば、カスタムメタクラスを使って、クラスがインスタンス化されたときのログを取ることができます。これは、メモリの使用量を抑えたいアプリケーションや、それを監視しなければならないアプリケーションにとって重要なことです。
ここでは、非常にシンプルなメタクラスの例として、シングルトンを紹介します。Singleton はよく知られたデザインパターンで、インターネット上にも多くの記述があります。しかし、ここではその技術的な価値ではなく、そのシンプルさを紹介したいと思います。
シングルトンの目的はただ一つ、オブジェクト指向のグローバル変数のように、インスタンス化されるたびに同じインスタンスを返すことです。そのため、呼び出されるたびに新しいインスタンスを返す標準クラスのようには動作しないクラスを構築する必要があります。
code: python
class Singleton(type):
instance = None
def __call__(cls, *args, **kw):
if not cls.instance:
cls.instance = super(Singleton, cls).__call__(*args, **kw)
return cls.instance
私たちは、Pythonクラスのすべての機能を提供するtypeを継承した新しいタイプを定義しています。私たちはクラスを呼び出すとき、つまりインスタンス化するときに呼び出される特別なメソッドである __call__ メソッドをオーバーライドします。新しいメソッドはインスタンス属性が設定されていないとき、つまりクラスが初めてインスタンス化されたときにのみ、元の型のメソッドを呼び出してラップし、そうでないときは記録されたインスタンスを返すだけです。ご覧の通り、これは非常に基本的なキャッシュクラスで、唯一のトリックはインスタンスの生成に適用されることです。
この新しい型をテストするためには、この型をメタクラスとして使用する新しいクラスを定義する必要があります。
code: python
>> class ASingleton(metaclass=Singleton):
... pass
...
>> a = ASingleton()
>> b = ASingleton()
>> a is b
True
>> hex(id(a))
'0xb68030ec'
>> hex(id(b))
'0xb68030ec'
is 演算子を使って、2つのオブジェクトがメモリ上で全く同じ構造であること、つまり、明示的に示されているようにIDが同じであることをテストします。実際には、a = ASingleton() を発行すると、ASingletonクラスは、クラスの背後にあるSingleton型から取得した__call__()メソッドを実行します。このメソッドは、インスタンスが生成されていないことを認識し(Singleton.instanceはNone)、標準的なクラスが行うのと同じように動作します。b = ASingleton()を発行すると、まったく同じことが起こりますが、Singleton.instance が None とは異なるため、その値(前のインスタンス)が直接返されます。
メタクラスは非常に強力なプログラミングツールであり、これを活用することで、わずかな労力で非常に複雑な動作を実現することができます。メタクラスの使用は、実際にメタプログラミングを行う際、つまりコードの動作を推進するコードを書く際には必ず必要です。例えば、クリエーショナルパターン(設定に応じてカスタムクラスの属性を注入する)、テスト、デバッグ、パフォーマンスモニタリングなどがその例です。
インスタンスの話
抽象基底クラスについて語ることでメタクラスの非常にスマートな使い方を紹介する前に、Pythonのオブジェクト生成手順、つまりクラスをインスタンス化するときに何が起こるのかについて掘り下げてみたいと思います。最初の投稿では、この手順は__init__()メソッドを見ることで、部分的にしか説明されていませんでした。
最初の記事では、オブジェクト指向の概念であるコンストラクタを思い出しました。コンストラクタとは、インスタンスが生成されるときに自動的に呼び出されるクラスの特別なメソッドです。また、クラスにはデストラクタを定義することもでき、これはオブジェクトが破壊されるときに呼び出されます。C++のようなガベージコレクション機構のない言語では、デストラクタは慎重に設計されなければなりません。Pythonではデストラクタは__del__()メソッドで定義できますが、ほとんど使われません。
逆に、Pythonのコンストラクタメカニズムは非常に重要で、1つのメソッドではなく、2つのメソッドで実装されています。__new__() と __init__() です。__new__() は新しいインスタンスを生成するときに必要な処理を行い、__init__() はオブジェクトの初期化を行います。
Pythonではその動的な性質のために属性を宣言する必要がないので、__new__() はプログラマーによって定義されることはほとんどなく、通常の作業の大部分は__init__に頼ることになります。__new__() の典型的な使い方は、前のセクションで挙げたものと非常によく似ています。というのも、クラスがインスタンス化されるたびに何らかのコードを起動することができるからです。
標準的な __new__() のオーバーライド方法は
code: python
class MyClass():
def __new__(cls, *args, **kwds):
obj = super().__new__(cls, *args, **kwds)
return obj
通常の__init__()と同じようにします。objectを継承したクラスでは、親メソッド(object.__init__())は空なので呼び出す必要はありませんが、 __new__をオーバーライドするときには呼び出す必要があります。
しかし、__new__()をオーバーライドするときには、その必要があります。__new__() は、それが定義されているクラスのインスタンスを返さなければならないわけではないことを覚えておいてください。とにかく、__init__() はコンテナ・クラスのインスタンスを返した場合にのみ呼び出されます。また、__new__() は __init__() とは異なり、最初のパラメータとしてクラスを受け取ることに注意してください。Pythonでは名前は重要ではなく、self と呼ぶこともできますが、インスタンスではないことを覚えておくために、cls を使う価値があります。
映画のトリビア
セクションのタイトルは以下の映画に由来しています。
The Blues Brothers (1980): The Type Brothers / タイプ・ブラザーズ
この映画タイトルの邦題は「ブルース・ブラザース」
The Muppets Take Manhattan (1984): The Metaclasses Take Python / Pythonのメタクラスについて
この映画タイトルの邦題は「マペットめざせブロードウェイ」
Terminator 2: Judgement Day (1991): Metaclasses 2: Singleton Day / メタクラス 2: シングルトンの日
この映画タイトルの邦題は「ターミネーター2」と味気ないもの
Coming to America (1988): Coming to Instance / インスタンスの話
この映画タイトルの邦題は「星の王子 ニューヨークへ行く」という超訳です。
参考資料
Python3によるオブジェクト指向プログラミングシリーズ
日本語訳:Python3によるオブジェクト指向プログラミング - メタクラス